/*
 * User: user
 * Date: 23/06/2008
 * Time: 00:16:54
 */
package org.conann.configuration;

import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Lists;
import nu.xom.*;
import org.conann.exceptions.WebBeansConfigurationException;
import org.conann.exceptions.WebBeansSystemException;
import static org.conann.metadata.AnnotationType.deployment;
import org.conann.util.ReflectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.webbeans.deployment.Standard;
import javax.webbeans.types.DeploymentType;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.List;

public class ConfigurationLoader {

    private static final Logger log = LoggerFactory.getLogger(ConfigurationLoader.class);

    public Configuration loadConfiguration(String path) {
        checkNotNull(path);
        Document document = getDocument(path);
        Element rootElelment = document.getRootElement();
        try {
            return new Configuration(getEnabledDeploymentTypes(rootElelment));
        } catch (WebBeansConfigurationException e) {
            throw new WebBeansConfigurationException(e, "Error reading configuration from file: %s\nThe error encountered:\n%s", path, e.getMessage());
        }
    }

    private List<Class<? extends Annotation>> getEnabledDeploymentTypes(Element root) {
        List<Class<? extends Annotation>> types = getAnnotationTypes(root, "deploy", "deployment-type", new ElementValidator<Class<? extends Annotation>>() {
            public void validate(Class<? extends Annotation> type, Node node) {
                if (!deployment.is(type)) {
                    throw new WebBeansConfigurationException("%s is not a deployment type.\nDeployment types must be annotated with @%s annotation.\nPlease check your XML configuartion.\nFollowing is the faulty element:\n%s", type.getName(), DeploymentType.class.getSimpleName(), node.toXML());
                }
            }
        });
        if (!types.isEmpty() && !types.contains(Standard.class)) {
            throw new WebBeansConfigurationException("@%s deployment type must be specified", Standard.class.getSimpleName());
        }
        return types;
    }

    @SuppressWarnings({"unchecked"})
    private <T extends Class<? extends Annotation>> List<T> getAnnotationTypes(Element root, String listElementName, String elementNodesName, ElementValidator<T> validator) {
        Elements possibleListNodes = root.getChildElements(listElementName);
        if (possibleListNodes.size() > 1) {
            throw new WebBeansConfigurationException("Only a single instance of 1 '%s' can be specified.\nPlease use the schema to validate your XML document.", listElementName);
        }
        List<T> deploymentTypes = Lists.newArrayList();
        if (possibleListNodes.size() == 0) {
            log.info(String.format("No '%s' element was specified - using defaults.", listElementName));
            return deploymentTypes;
        }
        Element listElement = possibleListNodes.get(0);
        Elements elements = listElement.getChildElements(elementNodesName);
        for (int i = 0; i < elements.size(); i++) {
            Element element = elements.get(i);
            String typeName = element.getValue();
            T type;
            try {
                type = (T) ReflectionUtil.resolveClass(typeName);
            } catch (WebBeansSystemException e) {
                throw new WebBeansConfigurationException(e, "Could not resolve type: %s.\n'%s/%s' elements must refer to annotation types.\nPlease check the following element:\n%s", typeName, listElement.getLocalName(), element.getLocalName(), element.toXML());
            } catch (ClassCastException e) {
                throw new WebBeansConfigurationException(e, "%s is not an annotation type.\n'%s/%s' elements must refer to annotation types.\nPlease check the following element:\n%s", typeName, listElement.getLocalName(), element.getLocalName(), element.toXML());
            }
            validator.validate(type, element);
            deploymentTypes.add(type);
        }
        return deploymentTypes;
    }

    private Document getDocument(String path) {
        InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(path);
        try {
            return new Builder().build(inputStream);
        } catch (ParsingException e) {
            final StringBuilder sb = new StringBuilder();
            sb.append("Parsing error: ");
            sb.append(e.getMessage());
            if (e.getURI() != null) {
                sb.append("\nResource: ");
                sb.append(e.getURI());
            }
            sb.append("\nIn line ");
            sb.append(e.getLineNumber());
            sb.append(", column ");
            sb.append(e.getColumnNumber());
            throw new WebBeansConfigurationException(e, "Can't parse file %s\n%s", path, sb.toString());
        } catch (IOException e) {
            throw new WebBeansConfigurationException(e, "Error openning file %s", path);
        } catch (XMLException e) {
            throw new WebBeansConfigurationException(e, "No satisfactory XML parser is installed in the local class path.");
        }
    }

    private interface ElementValidator<T> {

        void validate(T value, Node node);
    }
}